﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Kokomo.Mirror.CliMetadata;
using System.Reflection;
using System.IO;
using Kokomo.Mirror.CliMetadata.DebugInfo;

namespace Kokomo.Mirror.Test
{
	[TestClass]
	public class UnitTest1
	{
		public static Assembly baselineAssembly;
		public static Assembly testAssembly;
		public static Module baselineModule;
		public static Module testModule;
		static TestClass testClassObject;
		static Type testClassType;
		static Type mirrorTestClassType;
		static Type genericTestClassType;
		static Type mirrorGenericTestClassType;

		[ClassInitialize]
		public static void InitializeClass(TestContext context)
		{
			baselineAssembly = typeof(TestClass).Assembly;
			baselineModule = baselineAssembly.ManifestModule;

			testAssembly = baselineAssembly.Mirror();
			testModule = testAssembly.ManifestModule;

			testClassObject = new TestClass();

			testClassType = typeof(TestClass);
			mirrorTestClassType = testClassType.Mirror();
		}

		private static void CompareAssemblyProperties(
			Assembly x,
			Assembly y
			)
		{
			Assert.AreEqual(x.Location, y.Location);
			Assert.AreEqual(x.CodeBase, y.CodeBase, true);
			Assert.AreEqual(x.EscapedCodeBase, y.EscapedCodeBase, true);
			Assert.AreEqual(x.ImageRuntimeVersion, y.ImageRuntimeVersion);
			Assert.AreEqual(x.FullName, y.FullName);
		}

		[TestMethod]
		public void AssemblyPropertiesTest()
		{
			CompareAssemblyProperties(baselineAssembly, testAssembly);
		}

		private static void CompareModuleProperties(
			Module x,
			Module y
			)
		{
			Assert.AreEqual(x.FullyQualifiedName, y.FullyQualifiedName);
			Assert.AreEqual(x.MDStreamVersion, y.MDStreamVersion);
			Assert.AreEqual(x.MetadataToken, y.MetadataToken);
			Assert.AreEqual(x.ModuleVersionId, y.ModuleVersionId);
			Assert.AreEqual(x.Name, y.Name);
			Assert.AreEqual(x.ScopeName, y.ScopeName);
			Assert.AreEqual(x.MetadataToken, y.MetadataToken);
			CompareAssemblyProperties(x.Assembly, y.Assembly);
		}

		[TestMethod]
		public void ModulePropertiesTest()
		{
			Module baselineModule = typeof(object).Module;
			Module mirrorModule = baselineModule.Mirror();

			CompareModuleProperties(baselineModule, mirrorModule);
		}

		private static void CompareMemberProperties(
			MemberInfo x,
			MemberInfo y
			)
		{
			Assert.AreEqual(x.Name, y.Name);
			Assert.AreEqual(x.DeclaringType, y.DeclaringType);
			Assert.AreEqual(x.MemberType, y.MemberType);
			Assert.AreEqual(x.MetadataToken, y.MetadataToken);
			CompareModuleProperties(x.Module, y.Module);
			Assert.AreEqual(x.ReflectedType, y.ReflectedType);
		}
		private static void CompareTypeProperties(
			Type x,
			Type y
			)
		{
			CompareMemberProperties(x, y);
			CompareAssemblyProperties(x.Assembly, y.Assembly);
			Assert.AreEqual(x.AssemblyQualifiedName, y.AssemblyQualifiedName);
			Assert.AreEqual(x.Attributes, y.Attributes);
			if (x.BaseType == null)
				Assert.IsNull(y.BaseType);
			else
				CompareTypeProperties(x.BaseType, y.BaseType);
			Assert.AreEqual(x.ContainsGenericParameters, y.ContainsGenericParameters);
			if (x.IsGenericParameter)
			{
				Assert.AreEqual(x.DeclaringMethod, y.DeclaringMethod);
				Assert.AreEqual(x.GenericParameterAttributes, y.GenericParameterAttributes);
				Assert.AreEqual(x.GenericParameterPosition, y.GenericParameterPosition);
			}
			Assert.AreEqual(x.FullName, y.FullName);
			Assert.AreEqual(x.HasElementType, y.HasElementType);
			Assert.AreEqual(x.IsArray, y.IsArray);
			Assert.AreEqual(x.IsByRef, y.IsByRef);
			Assert.AreEqual(x.IsCOMObject, y.IsCOMObject);
			Assert.AreEqual(x.IsContextful, y.IsContextful);
			Assert.AreEqual(x.IsGenericParameter, y.IsGenericParameter);
			Assert.AreEqual(x.IsGenericType, y.IsGenericType);
			Assert.AreEqual(x.IsGenericTypeDefinition, y.IsGenericTypeDefinition);
			Assert.AreEqual(x.IsImport, y.IsImport);
			Assert.AreEqual(x.IsPointer, y.IsPointer);
			Assert.AreEqual(x.IsPrimitive, y.IsPrimitive);
			Assert.AreEqual(x.Namespace, y.Namespace);
			Assert.AreEqual(x.UnderlyingSystemType, y.UnderlyingSystemType);
			//Assert.AreEqual(x.GUID, y.GUID);
		}

		#region Type tests

		[TestMethod]
		public void TypePropertiesTest1()
		{
			CompareTypeProperties(typeof(object), typeof(object).Mirror());
			CompareTypeProperties(typeof(string), typeof(string).Mirror());
		}

		[TestMethod]
		public void GenericTypeTest()
		{
			Type listDefinitionType = typeof(List<>);
			CompareTypeProperties(listDefinitionType, listDefinitionType.Mirror());
			Type genericArg = listDefinitionType.GetGenericArguments()[0];
			CompareTypeProperties(genericArg, genericArg.Mirror());
			Type genericInstType = listDefinitionType.MakeGenericType(typeof(object));
			CompareTypeProperties(genericInstType, listDefinitionType.MakeGenericType(typeof(object)));

			Type genericArgBaseType = genericArg.BaseType;
			MemberInfo[] genericArgMembers = genericArg.GetMembers();

			Type mirrorListDefinitionType = listDefinitionType.Mirror();
			Type genericInstantiation = mirrorListDefinitionType.MakeGenericType(typeof(string));
		}

		#endregion

		[TestMethod]
		public void MethodPropertiesTest()
		{
			MethodInfo baselineMethod = testClassType.GetMethod("MethodWithParams");
			MethodInfo mirrorMethod = mirrorTestClassType.GetMethod("MethodWithParams");

			Assert.AreEqual(baselineMethod.ReturnType, mirrorMethod.ReturnType);
		}

		[TestMethod]
		public void GetTypesTest()
		{
			Type[] types = testAssembly.GetTypes();
		}
		[TestMethod]
		public void GetTypeTest()
		{
			Type testClassType = typeof(TestClass);
			Type mirrorTestClassType = testAssembly.GetType(testClassType.FullName);
		}

		[TestMethod]
		public void GetPropertiesTest()
		{
			PropertyInfo[] properties = mirrorTestClassType.GetProperties();
		}

		[TestMethod]
		public void GetMethodsTest()
		{
			MethodInfo[] methods = testClassType.GetMethods();
			MethodInfo[] mirrorMethods = mirrorTestClassType.GetMethods();

			bool methodsMatch = mirrorMethods.SequenceEqual(methods);
			Assert.IsTrue(methodsMatch);
		}

		[TestMethod]
		public void GetFieldsTest()
		{
			FieldInfo[] fields = mirrorTestClassType.GetFields();
		}

		[TestMethod]
		public void GetMethodTest1()
		{
			MethodInfo method = ((Action)TestClass.StaticMethod).Method;

			Type mirrorType = testAssembly.GetType(method.ReflectedType.FullName);
			MethodInfo mirrorMethod = mirrorType.GetMethod(method.Name);
		}

		[TestMethod]
		public void BaseTypeTest()
		{
			Type mirrorType = testAssembly.GetType(typeof(TestClass).FullName);
			Type baseType = mirrorType.BaseType;
		}

		[TestMethod]
		public void DebugDatabaseLoadTest()
		{
			string debugDatabaseFileName = Path.ChangeExtension(baselineAssembly.Location, ".pdb");
			DebugDatabase debugDatabase = DebugDatabase.Load(debugDatabaseFileName);
		}
	}
}
